{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cyclic Iterators"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Iterables do not have to be finite. In fact we can easily create an infinite cyclical iterator."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's an example - suppose we have a loop that iterates over some range of integers. As we loop through those integers we want to create a tuple containing the integer and a string that cycles over a finite set (smaller than the list of integers).\n",
    "\n",
    "```\n",
    "1, 2, 3, 4, 5, 6, 7, 8, 9, ...\n",
    "\n",
    "N, S, W, E\n",
    "```\n",
    "\n",
    "and we want to generate\n",
    "\n",
    "```\n",
    "1N, 2S, 3W, 4E, 5N, 6S, 7W, 8E, 9N, ...\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We could do it this way by creating a custom iterator for the list `['N', 'S', 'W', 'E']` that will cycle over that list indefinitely:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class CyclicIterator:\n",
    "    def __init__(self, lst):\n",
    "        self.lst = lst\n",
    "        self.i = 0\n",
    "        \n",
    "    def __iter__(self):\n",
    "        return self\n",
    "    \n",
    "    def __next__(self):\n",
    "        result = self.lst[self.i % len(self.lst)]\n",
    "        self.i += 1\n",
    "        return result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "iter_cycl = CyclicIterator('NSWE')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "N\n",
      "S\n",
      "W\n",
      "E\n",
      "N\n",
      "S\n",
      "W\n",
      "E\n",
      "N\n",
      "S\n"
     ]
    }
   ],
   "source": [
    "for i in range(10):\n",
    "    print(next(iter_cycl))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, now we can tackle our original problem:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1N\n",
      "2S\n",
      "3W\n",
      "4E\n",
      "5N\n",
      "6S\n",
      "7W\n",
      "8E\n",
      "9N\n",
      "10S\n"
     ]
    }
   ],
   "source": [
    "n = 10\n",
    "iter_cycl = CyclicIterator('NSWE')\n",
    "for i in range(1, n+1):\n",
    "    direction = next(iter_cycl)\n",
    "    print(f'{i}{direction}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And re-working this into a list comprehension:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['1N', '2S', '3W', '4E', '5N', '6S', '7W', '8E', '9N', '10S']"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n = 10\n",
    "iter_cycl = CyclicIterator('NSWE')\n",
    "[f'{i}{next(iter_cycl)}' for i in range(1, n+1)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, there's an easy alternative way to do this as well, using:\n",
    "* repetition\n",
    "* zip\n",
    "* a list comprehension"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We need to repeat the array ['N', 'S', 'W', 'E'] for as many times as we have elements in our range of integers - we can even create way more than we need - because when we `zip` it up with the range of integers, the smallest length iterable will be used:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(1, 'N'),\n",
       " (2, 'S'),\n",
       " (3, 'W'),\n",
       " (4, 'E'),\n",
       " (5, 'N'),\n",
       " (6, 'S'),\n",
       " (7, 'W'),\n",
       " (8, 'E'),\n",
       " (9, 'N'),\n",
       " (10, 'S')]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n = 10\n",
    "list(zip(range(1, n+1), 'NSWE' * (n//4 + 1)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['1N', '2S', '3W', '4E', '5N', '6S', '7W', '8E', '9N', '10S']"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[f'{i}{direction}'\n",
    " for i, direction in zip(range(1, n+1), 'NSWE' * (n//4 + 1))]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There's actually an even easier way yet, and that's to use our `CyclicIterator`, but instead of building it ourselves, we can simply use the one provided by Python in the standard library!!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import itertools"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['1N', '2S', '3W', '4E', '5N', '6S', '7W', '8E', '9N', '10S']"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n = 10\n",
    "iter_cycl = CyclicIterator('NSWE')\n",
    "[f'{i}{next(iter_cycl)}' for i in range(1, n+1)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "and using itertools:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['1N', '2S', '3W', '4E', '5N', '6S', '7W', '8E', '9N', '10S']"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n = 10\n",
    "iter_cycl = itertools.cycle('NSWE')\n",
    "[f'{i}{next(iter_cycl)}' for i in range(1, n+1)]"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
